package modele.pion;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;
import modele.Dessinable;
import modele.Drapeau;
import modele.Joueur;
import modele.Plateau;
import modele.case_.Case;
import modele.case_.CaseVide;
import modele.coupSpecial.CoupSpecial;
import modele.pion.aerien.Aerien;
import modele.pion.aerien.AvionDeChasse;
import outils.Outils;
import outils.aleatoire.LoiBinomiale;
import outils.chemin.Dijkstra;
import outils.gestionnairefichier.GestionnaireImage;

/**
 * Représente un pion présent sur le plateau
 *
 * @author gervaila
 */
public abstract class Pion implements Dessinable {

	/**
	 * le Joueur au quel le pion appartient
	 */
	protected Joueur joueur;
	/**
	 * la vie restante du pion
	 */
	protected int pv;
	/**
	 * la vie maximale du pion
	 */
	protected int pvMax;
	/**
	 * la portee maximale de ses attaques
	 */
	protected double portee;
	/**
	 * la puissance de ses attaques
	 */
	protected int puissance;
	/**
	 * la distance maximale à laquelle le pion peut se déplacer
	 */
	protected double rayon;
	/**
	 * la probabilité de réussir son attaque
	 */
	protected LoiBinomiale loiBinomialeAttaque;
	/**
	 * le pourcentage de sa vie maximale que le pion va transmettre si il meurt
	 */
	protected float dissipation;
	/**
	 * le prix du pion
	 */
	protected int prix;
	/**
	 * la case sur laquelle est positionné le pion
	 */
	protected Case maCase;
	/**
	 * l'adresse de l'image de ce pion
	 */
	protected String image;
	/**
	 * défini si le pion a déjà effectué ou non son attaque
	 */
	protected boolean attaqueEffectuee;
	/**
	 * défini si le pion a déjà effectué ou non son déplacement
	 */
	protected boolean deplacementEffectue;
	/**
	 * le coup spécial du pion
	 */
	protected CoupSpecial coupSpecial;
	/**
	 *
	 */
	protected Drapeau drapeau;

	/**
	 *
	 */
	public Pion() {
	}

	/**
	 * Crée un Pion
	 *
	 * @param joueur le joueur possédant ce pion
	 * @param pv la vie du pion
	 * @param portee la portee maximale des attaques
	 * @param puissance la puissance des attaques
	 * @param rayon la distance a laquelle le pion peut se déplacer
	 * @param probabiliteAttaque le pourcentage de réussite de son attaque
	 * @param dissipation le pourcentage de vie que le pion transmet quand il
	 * meurt
	 * @param prix le prix du piion
	 * @param maCase la case sur lequel est positonné le pion
	 * @param image l'adresse de l'image du pion
	 * @param monCoupSpecial le coup spécial du pion
	 */
	public Pion(Joueur joueur, int pv, double portee, int puissance, double rayon, float probabiliteAttaque, float dissipation, int prix, Case maCase, String image, CoupSpecial monCoupSpecial) {
		this.joueur = joueur;
		this.pv = pv;
		pvMax = pv;
		this.portee = portee;
		this.puissance = puissance;
		this.rayon = rayon;
		loiBinomialeAttaque = new LoiBinomiale(probabiliteAttaque);
		this.dissipation = dissipation;
		this.prix = prix;
		this.maCase = maCase;
		this.image = image;
		this.coupSpecial = monCoupSpecial;
		if (monCoupSpecial != null) {
			monCoupSpecial.setLanceur(this);
		}
	}

	/**
	 *
	 * @param classePion la classe du pion que l'on souhaite récupérer
	 * @return une instance sans joueur ni case du pion 'classePion'
	 */
	public static Pion getTypePion(Class classePion) {
		return getTypePion(classePion, null, null);
	}

	/**
	 *
	 * @param classePion la classe du pion que l'on souhaite créer
	 * @param joueur le joueur qui va posséder le pion
	 * @param c la case sur lequel va être placé le pion
	 * @return une instance du pion 'classePion' appartenant a 'joueur' et
	 * positionné sur la case 'c'
	 */
	public static Pion getTypePion(Class classePion, Joueur joueur, Case c) {
		Constructor[] ctors = classePion.getDeclaredConstructors();
		Pion pion = null;
		try {
			pion = (Pion) ctors[0].newInstance(joueur, c);
		} catch (Exception ex) {
			Logger.getLogger(Pion.class.getName()).log(Level.SEVERE, null, ex);
		}
		return pion;
	}

	/**
	 *
	 * @return le joueur possédant le pion
	 */
	public Joueur getJoueur() {
		return joueur;
	}

	/**
	 * Défini 'joueur' comme propriétaire du pion
	 *
	 * @param joueur
	 */
	public void setJoueur(Joueur joueur) {
		this.joueur = joueur;
	}

	/**
	 * Défini les nouveaux pv du pion
	 *
	 * @param pv
	 */
	public void setPv(int pv) {
		this.pv = pv;
	}

	/**
	 *
	 * @return les pv du pion
	 */
	public int getPv() {
		return pv;
	}

	/**
	 *
	 * @return les pvMax du pion
	 */
	public int getPvMax() {
		return pvMax;
	}

	/**
	 * Défini les pvMax du pion
	 *
	 * @param pvMax
	 */
	public void setPvMax(int pvMax) {
		this.pvMax = pvMax;
	}

	/**
	 *
	 * @return la portée du pion
	 */
	public double getPortee() {
		return portee;
	}

	/**
	 *
	 * @return le puissance du pion
	 */
	public int getPuissance() {
		return puissance;
	}

	/**
	 *
	 * @return le rayon du pion
	 */
	public double getRayon() {
		return rayon;
	}

	/**
	 *
	 * @return la probabilité de réussite de l'attaque du pion
	 */
	public float getProbabiliteAttaque() {
		return loiBinomialeAttaque.getProbabilite();
	}

	/**
	 *
	 * @return le prix du pion
	 */
	public int getPrix() {
		return prix;
	}

	/**
	 * Défini la nouvelle case du pion
	 *
	 * @param maCase
	 */
	public void setCase(Case maCase) {
		this.maCase = maCase;
	}

	/**
	 *
	 * @return la case où est placé le pion
	 */
	public Case getCase() {
		return maCase;
	}

	/**
	 *
	 * @return l'abscisse de la case où est présent le pion
	 */
	public int getX() {
		return maCase.getX();
	}

	/**
	 *
	 * @return l'ordonnée de la case où est présent le pion
	 */
	public int getY() {
		return maCase.getY();
	}

	/**
	 *
	 * @return l'adresse de l'image du pion
	 */
	public String getImage() {
		return image;
	}

	/**
	 *
	 * @return le pourcentage de dissipation
	 */
	public float getDissipation() {
		return dissipation;
	}

	/**
	 *
	 * @return le coup spécial du pion
	 */
	public CoupSpecial getCoupSpecial() {
		return coupSpecial;
	}

	/**
	 * Défini le coup spécial du pion
	 *
	 * @param coupSpecial
	 */
	public void setCoupSpecial(CoupSpecial coupSpecial) {
		this.coupSpecial = coupSpecial;
	}

	@Override
	public abstract String toString();

	/**
	 *
	 * @param c la case où veut se rendre le pion
	 * @return vrai si le pion peut aller à la case 'c', faux sinon
	 */
	public boolean peutAcceder(Case c) {
		boolean peutAcceder = !(c instanceof CaseVide);
		if (joueur == null) {
			peutAcceder = peutAcceder && c.getDrapeau() == null;
		} else {
			peutAcceder = peutAcceder && (c.getDrapeau() == null || (c.getDrapeau().getNumeroJoueur() == joueur.getNumero() && peutPorterDrapeau()));
		}
		return peutAcceder;
	}

	/**
	 *
	 * @param p le pion à traverser
	 * @return vrai si le pion peut traverser le pion 'p', faux sinon
	 */
	public boolean peutTraverser(Pion p) {
		return joueur == p.getJoueur();
	}

	/**
	 *
	 * @param p le pion à attaquer
	 * @return vrai si le pion peut attaquer le pion 'p', faux sinon
	 */
	public boolean peutAttaquer(Pion p) {
		return !(p instanceof Aerien) && drapeau == null;
	}

	/**
	 * inflige les dégâts au pion 'p' selon la puissance
	 *
	 * @param p le pion à qui on inflige les dégâts
	 */
	public void infligerDegats(Pion p) {
		p.setPv(p.getPv() - this.getPuissance());
	}

	/**
	 * Attaque le pion 'p'
	 *
	 * @param p le pion à attaquer
	 * @return vrai si l'attaque a eu lieu, faux sinon
	 */
	public boolean attaque(Pion p) {
		if (loiBinomialeAttaque.resultat()) {
			infligerDegats(p);
			if (p.estDecede()) {
				p.deces();
				pv += p.dissipation * p.pvMax;
				if (pv > pvMax) {
					pvMax = pv;
				}
			}
			setAttaqueEffectuee(true);
			return true;
		} else {
			return false;
		}
	}

	/**
	 * Simule une attaque
	 *
	 * @param p le pion à attaquer
	 */
	public void attaqueSimu(Pion p) {
		if (loiBinomialeAttaque.resultat()) {
			p.setPv(p.getPv() - this.getPuissance());
			if (p.estDecede()) {
				pv += p.dissipation * p.pvMax;
				if (pv > pvMax) {
					pvMax = pv;
				}
			}
		}
	}

	/**
	 *
	 * @param p le plateau de jeu
	 * @return les cases accessibles par ce pion
	 */
	public Dijkstra getCasesAccessibles(Plateau p) {
		return new Dijkstra(this, p);
	}

	/**
	 *
	 * @param p le plateau de jeu
	 * @return les cases que le pion peut attaquer
	 */
	public ArrayList<Case> getCasesAttaquables(Plateau p) {
		ArrayList<Case> cases = new ArrayList<Case>();
		if (drapeau == null) {
			cases = Outils.cercle(maCase, p, portee + 1);
			ArrayList<Case> casesASupprimer = new ArrayList<Case>();
			for (Case c : cases) {
				Pion pion = c.getPion();
				if (pion != null && (pion.getJoueur() == joueur || !peutAttaquer(pion)) || c instanceof CaseVide || c.getDrapeau() != null) {
					casesASupprimer.add(c);
				}
			}
			cases.removeAll(casesASupprimer);
			return cases;
		} else {
			return cases;
		}

	}

	/**
	 *
	 * @param p le plateau de jeu
	 * @return les pions adverses que ce pion peut attaquer
	 */
	public ArrayList<Pion> getPionsAttaquables(Plateau p) {
		ArrayList<Pion> pionsAttaquables = new ArrayList<Pion>();
		for (Case c : getCasesAttaquables(p)) {
			Pion pion = c.getPion();
			if (pion != null && pion.getJoueur() != joueur && peutAttaquer(pion)) {
				pionsAttaquables.add(pion);
			}
		}
		return pionsAttaquables;
	}

	/**
	 * Le pion peut à nouveau attaquer et se déplacer
	 */
	public void nouveauTour() {
		attaqueEffectuee = false;
		deplacementEffectue = false;
	}

	/**
	 *
	 * @return vrai si le pion a déjà effectué son attaque, faux sinon
	 */
	public boolean isAttaqueEffectuee() {
		return attaqueEffectuee;
	}

	/**
	 * Défini si l'attaque a été effectué
	 *
	 * @param attaqueEffectuee
	 */
	public void setAttaqueEffectuee(boolean attaqueEffectuee) {
		this.attaqueEffectuee = attaqueEffectuee;
	}

	/**
	 *
	 * @return vrai si le pion a déjà effectué son déplacement, faux sinon
	 */
	public boolean isDeplacementEffectue() {
		return deplacementEffectue;
	}

	/**
	 * Défini si le déplacement a été effectué
	 *
	 * @param deplacementEffectue
	 */
	public void setDeplacementEffectue(boolean deplacementEffectue) {
		this.deplacementEffectue = deplacementEffectue;
	}

	/**
	 *
	 * @return la BufferedImage représentant le pion
	 */
	public BufferedImage getBufferedImage() {
		if (joueur != null) {
			return joueur.getImagePion(image);
		} else {
			return GestionnaireImage.getInstance().get(image);
		}
	}

	/**
	 *
	 * @return une ArrayList de String contenant toutes les caractéristiques
	 * actuelles du pion
	 */
	public ArrayList<String> getCaracteristiques() {
		ArrayList<String> caracteristiques = new ArrayList<String>();
		caracteristiques.add("Vie : " + getPv() + " / " + getPvMax() + "pv");
		caracteristiques.add("Force : " + getPuissance());
		caracteristiques.add("Réussite : " + getProbabiliteAttaque() * 100 + "%");
		caracteristiques.add("Héritage : " + (int) (getDissipation() * getPvMax()) + "pv");
		if (peutAttaquer(new AvionDeChasse(null, null))) {
			caracteristiques.add("Attaque aérienne");
		}
		return caracteristiques;
	}

	/**
	 * Fait mourrir le pion
	 */
	public void deces() {
		maCase.setPion(null);
		joueur.supprimerPion(this);
		if (drapeau != null) {
			maCase.setDrapeau(drapeau);
			drapeau = null;
		}
	}

	@Override
	public void dessiner(Graphics2D gr, vue.Camera camera) {
		Rectangle vue = camera.getRectangle();
		int x = maCase.getX();
		int y = maCase.getY();
		gr.drawImage(getBufferedImage(), null, x * Plateau.TAILLE_CASE - vue.x, y * Plateau.TAILLE_CASE - vue.y);

		// affichage de la barre de vie
		gr.setColor(Color.red);
		gr.fillRect(x * Plateau.TAILLE_CASE - vue.x + 1, y * Plateau.TAILLE_CASE - vue.y + 1, Plateau.TAILLE_CASE - 2, 3);
		gr.setColor(Color.green);
		gr.fillRect(x * Plateau.TAILLE_CASE - vue.x + 1, y * Plateau.TAILLE_CASE - vue.y + 1, (int) ((Plateau.TAILLE_CASE - 2) * (float) pv / pvMax), 3);
	}

	/**
	 *
	 * @return vrai si le pion est décédé, faux sinon
	 */
	public boolean estDecede() {
		return pv <= 0;
	}

	/**
	 *
	 * @return vrai si le pion peut porter le drapeau, faux sinon
	 */
	public boolean peutPorterDrapeau() {
		return false;
	}

	/**
	 *
	 * @return le drapeau si le pion en possède un, null sinon
	 */
	public Drapeau getDrapeau() {
		return drapeau;
	}

	/**
	 * affecte le drapeau 'drapeau' au pion et l'enlève de sa case
	 *
	 * @param drapeau
	 */
	public void setDrapeau(Drapeau drapeau) {
		this.drapeau = drapeau;
		if (drapeau != null) {
			drapeau.setPion(this);
			drapeau.setCase(null);
		}
	}
}
